#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem/path.hpp>
//#include <boost/program_options/detail/value_semantic.hpp>
#include <boost/regex.hpp>
#include <boost/tuple/tuple.hpp>

#include <boost/foreach.hpp>

#include <iostream>
#include <algorithm>
#define DEBUG_LEVEL 5
#include <debug_func.h>
using std::equal_to;
using std::max;
using std::sort;
using std::cerr;
#include <functional>
using std::bind2nd;
#include <vector>
using std::vector;
#include <string>
using std::string;
#include <sstream>
using std::ostringstream;
#include <ostream>
using std::ostream;

#include "stlhelper.h"
#include "indent_str.h"


#include "boost_program_options_helper.h"
namespace po = boost::program_options;




namespace boost_program_options_helper
{

std::pair<unsigned, unsigned> get_max_and_last_line_len(const string& str)
{
	vector<string> lines;
	boost::split(lines, str, bind2nd(equal_to<char>(), '\n'));
	if (!lines.size())
		lines.push_back(str);
	unsigned len = lines[0].length();
	BOOST_FOREACH(const string& s, lines) 
		if (len < s.length())
			len = s.length();
	return std::make_pair(len, lines.back().length());
}

string wrap_by_farthest_colon(const string& old_str, unsigned line_len, unsigned indent_sz = 4, unsigned post_colon_len = 30)
{
	assert(line_len >= post_colon_len);
	string str = old_str;

	// tabs to strings
	boost::replace_all(str, "\t", string(indent_sz, ' '));

	// split into separate lines
	vector<string> lines;
	boost::split(lines, str, std::bind2nd(std::equal_to<char>(), '\n'));

	//
	// find furthest colon positions
	//

	vector<string::size_type> colon_line_len(lines.size(), string::npos);
	vector<string::size_type> colon_pos(lines.size(), string::npos);

	// furthest position of colon. All colons will be forced to align here
	string::size_type max_colon_pos = 0;
	for (unsigned i = 0; i < lines.size(); ++i)
	{
		// continue if no colon
		colon_pos[i] = colon_line_len[i] = lines[i].find(": ");
		if (colon_line_len[i] == string::npos)
			continue;

		// Colon position reasonable. Save
		if (colon_line_len[i] < line_len - post_colon_len)
		{
			max_colon_pos = max(max_colon_pos, colon_line_len[i]);
			continue;
		}

		//
		// colon position too far: line too long. Wrap before colon
		//

		// Remove post colon part which does not need to be line wrapped
		string post_colon_part = lines[i].substr(colon_pos[i] + 2);

		// remove any excessive spaces before colon
		lines[i].erase(colon_pos[i]);
		lines[i].erase(lines[i].find_last_not_of(' ') + 1);
		lines[i] += ": ";

		// wrap pre colon line
		lines[i] = hanging_indent_with_line_wrap(lines[i], 8, line_len - post_colon_len - 2);

		unsigned max_line_len, last_line_len;
		boost::tie(max_line_len, last_line_len) =get_max_and_last_line_len(lines[i]);

		// the maximum colon position is the length of the longest line + 2
		max_colon_pos = max(max_colon_pos, max_line_len );

		// remember where the line lengths are
		colon_pos[i] = lines[i].length() - 2;
		colon_line_len[i] = last_line_len - 2;

		// add back non-wrapped post colon part of string
		lines[i] += post_colon_part;
	}

	// add spaces to align colons
	for (unsigned i = 0; i < lines.size(); ++i)
		if (colon_line_len[i] != string::npos && max_colon_pos != colon_line_len[i])
		{
			lines[i].insert(colon_pos[i], max_colon_pos - colon_line_len[i], ' ');
		}



	// for each line, split into sub-lines and wrap those by hanging indent past the
	// 		colon position
	for (unsigned i = 0; i < lines.size(); ++i)
	{
		// get string last newline and handle separately

		string::size_type last_newline = lines[i].rfind("\n");
		string wrap_part = lines[i].substr(last_newline + 1);
		lines[i].erase(last_newline + 1);


		// split by \1
		vector<string> sub_lines;
		boost::split(sub_lines, wrap_part, bind2nd(equal_to<char>(), '\1'));
		if (!sub_lines.size())
			sub_lines.push_back(wrap_part);


		// wrap the first line
		sub_lines[0] = hanging_indent_with_line_wrap(sub_lines[0], max_colon_pos + 2, line_len);


		// wrap the subsequent lines after indenting past the colon position
		for (unsigned j = 1; j < sub_lines.size(); ++j)
			sub_lines[j] = hanging_indent_with_line_wrap(string(max_colon_pos + 2, ' ') + sub_lines[j],
										max_colon_pos + 2, line_len);

		// join back up
		//lines[i].clear();
		for (unsigned j = 0; j < sub_lines.size(); ++j)
			lines[i] += sub_lines[j] + "\n";
	}

	// join back up
	str.clear();
	for (unsigned i = 0; i < lines.size(); ++i)
		str += lines[i];

	return str;
}


void print_version(ostream& os, const string& exe_name, const string& version, unsigned line_len)
{
	debug_func_cerr(5);
	string exe_file_name =  boost::filesystem::path(exe_name).leaf();
	unsigned indentation = 4;
	os << "\n" << string(indentation, ' ') << exe_file_name
		//<< exe_file_name << "    Version " + version + "\n\n";
	<< indent_string("Version " + version, indentation) << "\n";
}


//
//	Each line of exe_description is indented with 2 spaces
// 	Lines have further hanging indentation.
//  If a line break is desired with hanging indentation, use \1 instead of \n
//  Tabs are 4 spaces
//
void print_usage(ostream& os, const string& exe_name, const string& exe_param_line,
				po::options_description& options, string exe_description,
				unsigned line_len)
{
	debug_func_cerr(5);
	// get option descriptions
	ostringstream sstrm;
	sstrm << options;

	// wrap and indent exe_description
	vector<string> lines;
	boost::split(lines, exe_description, std::bind2nd(std::equal_to<char>(), '\n'));
	for (unsigned i = 0; i < lines.size(); ++i)
	{
		std::replace(lines[i].begin(),  lines[i].end(),  '\1',  '\n');
		// tabs to strings
		boost::replace_all(lines[i], "\t", string(4, ' '));
		lines[i] = "  " + hanging_indent_with_line_wrap(lines[i], 6, line_len - 2);
	}
	exe_description.clear();
	for (unsigned i = 0; i < lines.size(); ++i)
		exe_description += lines[i] + '\n';

	string exe_file_name = boost::filesystem::path(exe_name).leaf();
	os
		<< string(line_len, '-') << "\n\n"
		<< "Usage:\n"
   		<< hanging_indent_with_line_wrap(	"  " + exe_file_name + " " + exe_param_line + "\n",
											exe_file_name.length() + 3, line_len)
		<< wrap_by_farthest_colon(sstrm.str(), line_len)
		<< "\nDescription:\n"
		<< exe_description
		<< "\n"
		<< string(line_len, '-') << "\n\n";

}

void check_required_options(const po::variables_map& vm, const po::options_description& desc)
{
	debug_func_cerr(5);
	BOOST_FOREACH(boost::shared_ptr<po::option_description> opt, desc.options()) 
	{
		if (!vm.count(opt->long_name()))
			throw std::logic_error("Missing option [--" + opt->long_name() + "]");
	}
}

void parse_indices(const string& search_str, vector<unsigned>& indices)
{

	debug_func_cerr(5);
	boost::regex expression("^(\\d+)(?:\\s*\\-\\s*(\\d+))?[\\s,]*");
	boost::smatch what;
	string::const_iterator b = search_str.begin();
	string::const_iterator e = search_str.end();
	while(regex_search(b, e, what, expression))
	{

		if (what[2].matched)
		{
			unsigned end = atol(what[2].str().c_str());
			unsigned beg = atol(what[1].str().c_str());
			if (end >= beg)
				for (unsigned i = beg; i < end + 1; ++i)
					indices.push_back(i);
			else
				throw std::logic_error("Incorrectly specified index range \"" +
											what[0].str() +
										"\".\nIndex ranges must be specified as \"A-B\" "
											"where A <= B.");
		}
		else
			indices.push_back(atol(what[1].str().c_str()));
		b = what[0].second;
	}
//	sort(indices.begin(),  indices.end());
//	remove_duplicates(indices);
	if (b != e)
		throw std::logic_error("Indices should be separated by commas, "
								"with ranges denoted by hyphens.\n"
								"e.g.  \"1,3,9,11-13\".");
}



void validate(boost::any& v,
              const std::vector<std::string>& values,
              t_option_level* target_type, int)
{
    using namespace boost::program_options;

	if (values.size())
		throw validation_error("this option does not take a value.");
	t_option_level i;
	if (!v.empty())
		i = boost::any_cast<t_option_level>(v);
	v = boost::any(i.inc());
}


// validate unsigned
void validate(boost::any& v, 
              const std::vector<std::string>& values,
              unsigned* target_type, int)
{

	using namespace boost::program_options;
	using boost::lexical_cast;
	using boost::any;

    // Make sure no previous assignment to 'a' was made.
    validators::check_first_occurrence(v);
	
    // Extract the first string from 'values'. If there is more than
    // one string, it's an error, and exception will be thrown.
    const string& s = validators::get_single_string(values);

	// match a negative sign an a number
	static boost::regex r("(\\-)(\\d+)");
	
    // Do regex match and convert the interesting part to 
    // int.
	boost::smatch what;
    if (regex_match(s, what, r)) 
	{
		if (what[1].matched)
		{
			throw validation_error("This should not be a negative number.");
		}
        v = any(lexical_cast<unsigned>(what[2]));
    } 
	else 
	{
        throw validation_error("\"" + s + "\" is not a valid value.");
    }        
}

}
